
#在做了 飞常努力页面

import eventlet
# 打补丁，让 eventlet 可以异步处理网络请求
eventlet.monkey_patch()
import datetime
import logging
from flask import Flask, request, jsonify, g
from flask_jwt_extended import JWTManager, jwt_required, verify_jwt_in_request, get_jwt_identity
import config
from ResponseBody.ResponseBody import Response
from blueprint.book import BookApi
from extension import db, cors, socketio
from models.models import Book, UserModel, RoomModel, DetailModel
from blueprint.user import bp as user_bp
from blueprint.minisoft import bp as minisoft_bp
# from blueprint.socket import bp as socket_bp
from blueprint.room import bp as room_bp
from blueprint.mysqlcrud.user import bp as mysqlcrud_bp
from blueprint.mysqlcrud.room import bp as mysqlroom_bp
from blueprint.mysqlcrud.chart import bp as chart_bp
from flask_migrate import Migrate
import fnmatch  # 内置库，用于通配符匹配

def is_excluded(url, exclude_patterns):
    """
    判断请求路径是否在免除列表中（支持通配符*）
    :param url: 当前请求路径（如 /user/login）
    :param exclude_patterns: 免除规则列表（如 ["/user/*", "/api/*"]）
    :return: True（需要免除）/False（需要验证）
    """
    for pattern in exclude_patterns:
        # 使用fnmatch匹配通配符规则（*匹配任意字符，?匹配单个字符）
        if fnmatch.fnmatch(url, pattern):
            return True
    return False





#实例化flask对象
app = Flask(__name__)
# app.config['JSON_AS_ASCII'] = False
# app.config['JSONIFY_MIMETYPE'] = 'application/json; charset=utf-8'
#配置绑定
app.config.from_object(config)
# 配置：解决中文编码问题

#插件注册
db.init_app(app)#db对象绑定到app
cors.init_app(app)#cors绑定到app
migrate=Migrate(app,db)
socketio.init_app(app,cors_allowed_origins='*',async_mode=None)
# socketio.init_app(app,cors_allowed_origins='*',async_mode=“eventlet”)

#蓝图路由注册
app.register_blueprint(user_bp)
app.register_blueprint(minisoft_bp)
# app.register_blueprint(socket_bp)
app.register_blueprint(room_bp)



app.register_blueprint(mysqlcrud_bp)
app.register_blueprint(mysqlroom_bp)
app.register_blueprint(chart_bp)







#JWT鉴权
jwt = JWTManager(app)
##免过滤接口，这里写的是不需要经过jwt token验证的接口，如登录接口或者其他免登接口
exclude_path_patterns_list = [
    "/books/",
    "/user/login",
    "/minisoft/get_openid",
    "/minisoft/storeuserinfo",
    "/mysqlcrud",
    "/mysqlcrud/createuser",
    "/mysqlroom/create_room",
    "/mysqlroom/check_my_rooms",
    "/mysqlchart/get_test_data",
    "/mysqlchart/get_test_data1",
    "/mysqlroom/*",
    "/room/create_room",
    "/room/joinroom",
    "/socket/hello*",
    "/hello"
]
##拦截器，所有请求先经过这里，可以获取请求头token进行拦截
#装饰器函数用于在每次请求处理之前都会执行的函数。作用是：验证token有效性以及拿出token中的数据
@app.before_request
def my_before_request():
    # 日志打印
    url = request.path
    now_time = datetime.datetime.now()
    logging.info(f"{now_time}:访问接口：{url}")
    if url.startswith('/socket.io'):  # 跳过 Socket.IO 请求
        return
    if is_excluded(url, exclude_path_patterns_list):
        return
    try:
        # JWT 验证成功，解析 JWT 中的内容
        verify_jwt_in_request()
        # 在这里你可以把identity拿出来
        username = get_jwt_identity()
        # 在这里你可以把claims拿出来以及获取保存在token里的东西
        # msg = get_jwt()
        # adminId = msg.get('adminid')
        # 将获取到的信息保存到全局上下文中
        setattr(g, "username", username)
        # print(g.get("username"))
    except Exception as e:

        # 如果JWT验证失败，返回错误信息
        return Response.LOGIN_OUT(e.__str__()).to_dict()
##app.context_processor 装饰器定义了一个上下文处理器函数。这个函数的返回值会被添加到 Flask 应用的模板渲染上下文中。也就是说，在渲染 HTML 模板时，不需要每次都手动传递 adminId 和 adminPhone，模板就可以直接访问这些变量，使得在前端模板中使用当前用户相关信息变得更加便捷，提升开发效率。
@app.context_processor
def my_context_processor():
    return {"username": g.username}
#自定义鉴权信息，已弃用
# @jwt.unauthorized_loader
# def custom_missing_token_callback(error):
#     return jsonify({"msg": "token已失效！"}), 401

#思考一下这个验证令牌的函数是否有必要，有没有一种可能库中封装了这个功能
@app.route('/checkToken')
@jwt_required()
def checkToken():
    jwt_header = request.headers.get('Authorization')
    if not jwt_header:
        return jsonify({"message": "验证失败!", "code": 401, "data": False})
    try:
        # 验证请求中的JWT
        verify_jwt_in_request()
        # user_id = get_jwt_identity()
        return jsonify({"message": "验证成功!", "code": 200, "data": True})
    except:
        return jsonify({"message": "验证失败!", "code": 401, "data": False})

@app.route('/abc/')
def hello_world():  # put application's code here
    # return 'Hello World!'
    response = jsonify({"message": "验证失败!", "code": 401, "data": False})
    response.status_code =500
    return response

@app.route('/hello')
def hello():  # put application's code here

    return jsonify({"message": "测试中文", "data": "直接返回的中文字符"})
#自定义cmd命令
@app.cli.command()
def create():
    db.drop_all()#清空所有表
    db.create_all()#将orm对象映射到数据库创建表结构
    #初始化数据
    Book.init_db()
    UserModel.init_db()
    RoomModel.init_db()  # 初始化 Room 表
    DetailModel.init_db()  # 初始化 detailModel 表
#继承自MethodView的类中的函数名不能乱写。这些函数名（get、post、delete、put）是有特定含义的，它们需要与 HTTP 请求方法相对应，他是自动严格匹配的。
#定义一个视图类，继承自MethodView
# class BookApi(MethodView):
#     def get(self, book_id):
#         if not book_id:
#             books: [Book] = Book.query.all()#Book是数据库模型类，query是SQLAlchemy 为模型类提供的一个查询接口； ：使用类型注解（: [Book] ）表明 books 变量是一个由 Book 类型对象组成的列表
#             results = [
#                 {
#                     'id': book.id,
#                     'book_name': book.book_name,
#                     'book_type': book.book_type,
#                     'book_prize': book.book_prize,
#                     'book_number': book.book_number,
#                     'book_publisher': book.book_publisher,
#                     'author': book.author,
#                 } for book in books#列表推导式
#             ]
#             return {
#                 'status': 'success',
#                 'message': '数据查询成功',
#                 'results': results
#             }
#         book: Book = Book.query.get(book_id)
#         return {
#             'status': 'success',
#             'message': '数据查询成功',
#             'result': {
#                 'id': book.id,
#                 'book_name': book.book_name,
#                 'book_type': book.book_type,
#                 'book_prize': book.book_prize,
#                 'book_number': book.book_number,
#                 'book_publisher': book.book_publisher,
#                 'author': book.author,
#             }
#         }
#     def post(self):
#         form = request.json
#         book = Book()
#         book.book_number = form.get('book_number')
#         book.book_name = form.get('book_name')
#         book.book_type = form.get('book_type')
#         book.book_prize = form.get('book_prize')
#         book.author = form.get('author')
#         book.book_publisher = form.get('book_publisher')
#         db.session.add(book)
#         db.session.commit()
#         # id, book_number, book_name, book_type, book_prize, author, book_publisher
#         return {
#             'status': 'success',
#             'message': '数据添加成功'
#         }
#     def delete(self, book_id):
#         book = Book.query.get(book_id)
#         db.session.delete(book)
#         db.session.commit()
#         return {
#             'status': 'success',
#             'message': '数据删除成功'
#         }
#     def put(self, book_id):
#         book: Book = Book.query.get(book_id)
#         book.book_type = request.json.get('book_type')
#         book.book_name = request.json.get('book_name')
#         book.book_prize = request.json.get('book_prize')
#         book.book_number = request.json.get('book_number')
#         book.book_publisher = request.json.get('book_type')
#         book.author = request.json.get('book_type')
#         db.session.commit()
#         return {
#             'status': 'success',
#             'message': '数据修改成功'
#         }

#MethodView路由注册
#as_view是MethodView类提供的一个类方法，作用是将视图类转化为一个可被 Flask 应用调用的视图函数(即那种头上带@app.route装饰器的方法)，这里给它起名为book_api
book_api = BookApi.as_view('book_api')
#app是 Flask 应用实例，add_url_rule用于给 Flask 应用添加 URL 路由规则。这里将/books这个 URL 路径与前面生成的视图函数book_api关联起来。当客户端访问/books且请求方法是GET时，就会触发对应的处理逻辑。defaults参数很关键，它设置了默认参数值。这里设置book_id为None，意味着当客户端发起对/books的 GET 请求时，视图函数接收到的book_id参数默认是None，这通常用来区分是获取所有书籍（book_id为None）还是获取单本特定书籍（book_id有具体值）的请求。
#URL不传参默认查所有、带参请求
app.add_url_rule('/books/', view_func=book_api, methods=['GET', ], defaults={'book_id': None})
app.add_url_rule('/books', view_func=book_api, methods=['POST', ])
app.add_url_rule('/books/<int:book_id>', view_func=book_api, methods=['GET', 'PUT', 'DELETE'])


if __name__ == '__main__':
    # app.run(debug=True)
    # socketio.run(app)
    print("Flask 应用启动中...")  # 测试打印
    # # 打印当前的异步模式
    print(f"Flask-SocketIO 异步模式: {socketio.async_mode}")
    # 破案了，直接通过pycharm运行是flaskrun方式不运行main方法，所以这里打印不出来因为没有执行main方法，故而报错说没有正确是使用eventlet
    socketio.run(app, host='172.16.24.120',  debug=True)
    # socketio.run(app, host='0.0.0.0',  debug=True)
    # socketio.run(app, host='127.0.0.1',  debug=True)

# 为什么我这里使用flask run命令行工具启动
# 1none会存在异步的问题
# or
# 2eventlet会存在错误使用的问题？他那里为什么不会有问题？
# 可能的猜测：首先他没有配置eventlet使用是自动选择的，其次我的代码里可能存在问题，因为当我不配置时候其实也是eventlet，但是存在异步的问题

